Add large-payload blob auto-purge (opt-in singleton job, worker/SDK side)#758
Draft
YunchuWang wants to merge 1 commit into
Draft
Add large-payload blob auto-purge (opt-in singleton job, worker/SDK side)#758YunchuWang wants to merge 1 commit into
YunchuWang wants to merge 1 commit into
Conversation
82ae04d to
0ac2dc3
Compare
0752610 to
cab0e9a
Compare
Comment on lines
+121
to
+128
| foreach (DeleteOutcome outcome in outcomes) | ||
| { | ||
| // Only acknowledge blobs that were actually deleted; failed tokens stay tombstoned to retry. | ||
| if (outcome.Deleted) | ||
| { | ||
| deleted.Add(outcome.Ack); | ||
| } | ||
| } |
Large orchestration payloads are externalized to Azure Blob Storage as `blob:v1:<container>:<blobName>` tokens. The DTS backend stores those tokens but cannot delete the backing blobs (it has no storage credentials) — only this SDK can. This adds an opt-in, whole-scheduler singleton durable entity + orchestration job (mirroring src/ExportHistory) that drains payload rows the backend has soft-deleted and deletes their blobs, then acks so the backend can hard-delete the rows. Design: - PayloadStore.DeleteAsync is virtual (default throws NotSupportedException so it is non-breaking for existing external subclasses); BlobPayloadStore overrides it to decode the token and call DeleteIfExistsAsync (idempotent). - BlobPurgeJob (TaskEntity singleton): Create is a no-op when already Active so racing client processes don't disturb the running job; Run starts a fixed-id orchestrator. - BlobPurgeJobOrchestrator (perpetual): fetch a batch of tombstones, delete the blobs with capped parallelism, ack the successful deletions (failed tokens stay tombstoned to retry), idle on a timer when empty, ContinueAsNew periodically. - ExecuteBlobPurgeJobOperationOrchestrator bridges client -> entity. - Two new unary RPCs on TaskHubSidecarService: GetTombstonedPayloads / AckPurgedPayloads (authoritative proto follow-up: microsoft/durabletask-protobuf#76). - LargePayloadStorageOptions gains AutoPurge (opt-in, default false) and PayloadPurgeBatchSize (default 500). - Client-side BlobPurgeJobStarter (IHostedService) ensures the singleton job when AutoPurge is enabled, without blocking host startup. Worker always registers the entity/orchestrators/activities so a client-enabled job has something to run. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
cab0e9a to
c05b15a
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Large orchestration payloads are externalized to Azure Blob Storage by the
AzureBlobPayloadsextension asblob:v1:<container>:<blobName>tokens. The DTS backend stores those tokens but cannot delete the backing blobs — it has no storage credentials; only this SDK can. This PR implements the worker/SDK side of large-payload blob auto-purge.Design (opt-in singleton durable entity + orchestration job)
Instead of an always-on background stream, this mirrors the existing
src/ExportHistoryfeature: an opt-in, whole-scheduler singleton durable entity + orchestration job that drains soft-deleted payload rows the backend exposes and deletes their blobs, then acks so the backend can hard-delete the rows.PayloadStore.DeleteAsync— added as avirtualmethod (default throwsNotSupportedException, so it is non-breaking for existing external subclasses).BlobPayloadStoreoverrides it to decode the token and callDeleteIfExistsAsync(idempotent — deleting a missing blob is a no-op).BlobPurgeJob(TaskEntitysingleton) —Createis a no-op when already Active so racing client processes don't disturb the running job (intentionally softer thanExportJob.Create, which throws).Runstarts a fixed-instance-id orchestrator.BlobPurgeJobOrchestrator(perpetual) — each cycle: fetch a batch of tombstones, delete the blobs with capped parallelism (32), ack only the successful deletions (failed tokens stay tombstoned to retry), idle on a 1-minute timer when there's nothing to purge, andContinueAsNewevery 5 cycles to keep history small. Activities use a small retry policy.ExecuteBlobPurgeJobOperationOrchestrator— client → entity bridge (mirrors export).GetTombstonedPayloadsActivity,DeleteExternalBlobActivity(returnsfalse+ logs on failure so one bad token can't fail the batch),AckPurgedPayloadsActivity.BlobPurgeBackendClient— resolves the worker's intercepted gRPCCallInvokerfrom the namedGrpcDurableTaskWorkerOptionsand calls the two new unary RPCs.LargePayloadStorageOptions.AutoPurge(opt-in, defaultfalse) andPayloadPurgeBatchSize(default500).BlobPurgeJobStarter(IHostedService) ensures the singleton job whenAutoPurgeis enabled, on a background task that does not block host startup and retries until the backend is reachable. The worker always registers the entity/orchestrators/activities (not gated onAutoPurge) so a client-enabled job always has something to execute.gRPC contract
Two new unary RPCs added to
TaskHubSidecarServiceinsrc/Grpc/orchestrator_service.proto(worker is the client; wire paths/TaskHubSidecarService/GetTombstonedPayloadsand/AckPurgedPayloads):C# stubs are generated at build time by
Grpc.Tools(not committed). The authoritative proto change is a follow-up in microsoft/durabletask-protobuf#76; once it merges,src/Grpc/orchestrator_service.protoshould be re-synced from upstream (content identical to what's here). The vendored change lets this PR build/test standalone.Testing
dotnet build Microsoft.DurableTask.sln— succeeds, 0 errors.dotnet test test/AzureBlobPayloads.Tests— 11 passed (BlobPayloadStore delete/idempotency +BlobPurgeJob.Createno-op-when-Active + options defaults).dotnet test test/ExportHistory.Tests— 147 passed (shared patterns unaffected).Notes / deviations
BlobPurgeJobStatus.Stoppedis the0/default value (mirroringExportJobStatus.Pending=0) so a freshly initialized entity never appearsActive.RecordPurgedentity op to track a cumulativePurgedCount.Co-authored-by: Copilot App 223556219+Copilot@users.noreply.github.com